#include "Renderer.h"
#include "VertexArray.h"
#include "../Shader.h"
#include "RendererCommand.h"
#include <glm/gtx/transform2.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <array>

namespace lazy {

    struct QuadVertex {
        glm::vec3 Position;
        glm::vec4 Color;
        float TexIndex;
        glm::vec2 TexCoord;

    };

    struct RendererData {
        static const uint32_t MaxQuads = 100;
        static const uint32_t MaxVertices = MaxQuads * 4;
        static const uint32_t MaxIndices = MaxQuads * 6;
        static const uint32_t MaxTextureSlots = 16;

        Ref<VertexArray> QuadVertexArray;
        Ref<VertexBuffer> QuadVertexBuffer;
        Ref<Shader> QuadShader;
        uint32_t QuadIndexCount = 0;
        QuadVertex* QuadVertexBufferBase = nullptr;
        QuadVertex* QuadVertexBufferPtr = nullptr;

        std::array<Ref<Texture2D>, MaxTextureSlots> TextureSlots;
        uint32_t TextureSlotIndex = 1;

        glm::vec4 QuadVertexPositions[4];
        glm::vec2 QuadVertexTexCoord[4];

        struct CameraData {
            glm::mat4 ViewProjection;
        };
        CameraData CameraBuffer;
        Ref<UniformBuffer> CameraUniformBuffer;
    };

    static RendererData Data;

    void Renderer::Init() {
        Data.QuadVertexArray = VertexArray::Create();
        Data.QuadVertexBuffer = VertexBuffer::Create(RendererData::MaxQuads * sizeof(QuadVertex));
        lazy::BufferLayout layout = {
                { lazy::ShaderDataType::Float3, "a_Position" },
                { lazy::ShaderDataType::Float4, "a_Color" },
                { lazy::ShaderDataType::Float, "a_TexIndex" },
                { lazy::ShaderDataType::Float2, "a_TexCoord" },
        };
        Data.QuadVertexBuffer->SetLayout(layout);
        Data.QuadVertexArray->AddVertexBuffer(Data.QuadVertexBuffer);
        Data.QuadVertexBufferBase = new QuadVertex[RendererData::MaxVertices];
        Data.QuadShader = Shader::LoadFromFile("resources/Shaders/quad.vert", "resources/Shaders/quad.frag");
        Data.TextureSlots[0] = lazy::Texture2D::LoadFromFile("resources/images/wall.jpg");

        auto quadIndices = new uint32_t[RendererData::MaxIndices];
        uint32_t offset = 0;
        for (int i = 0; i < RendererData::MaxIndices; i += 6) {
            quadIndices[i + 0] = offset + 0;
            quadIndices[i + 1] = offset + 1;
            quadIndices[i + 2] = offset + 2;

            quadIndices[i + 3] = offset + 2;
            quadIndices[i + 4] = offset + 3;
            quadIndices[i + 5] = offset + 0;
            offset += 4;
        }
        Ref<IndexBuffer> quadIndexBuffer = IndexBuffer::Create(quadIndices, RendererData::MaxIndices);
        Data.QuadVertexArray->SetIndexBuffer(quadIndexBuffer);
        delete[] quadIndices;

        Data.QuadVertexPositions[0] = {-1.0f, -1.0f, 0.0f, 1.0f};
        Data.QuadVertexPositions[1] = {1.0f, -1.0f, 0.0f, 1.0f};
        Data.QuadVertexPositions[2] = {1.0f, 1.0f, 0.0f, 1.0f};
        Data.QuadVertexPositions[3] = {-1.0f, 1.0f, 0.0f, 1.0f};

        Data.QuadVertexTexCoord[0] = {0.0f, 0.0f};
        Data.QuadVertexTexCoord[1] = {1.0f, 0.0f};
        Data.QuadVertexTexCoord[2] = {1.0f, 1.0f};
        Data.QuadVertexTexCoord[3] = {0.0f, 1.0f};

        Data.CameraUniformBuffer = UniformBuffer::Create(sizeof(RendererData::CameraData), 0);
    }

    void Renderer::BeginScene(float ratio) {
        auto view = glm::lookAt(glm::vec3(.0f, .0f, 10.0f), glm::vec3(0, 0,0), glm::vec3(0, 1, 0));
        auto projection = glm::perspective(glm::radians(60.f), ratio, 1.f, 1000.f);
        Data.CameraBuffer.ViewProjection = projection * view;
        Data.CameraUniformBuffer->SetData(&Data.CameraBuffer, sizeof(RendererData::CameraData));

        Data.QuadIndexCount = 0;
        Data.QuadVertexBufferPtr = Data.QuadVertexBufferBase;
    }

    void Renderer::EndScene() {
        if (Data.QuadIndexCount) {
            uint32_t dataSize = (Data.QuadVertexBufferPtr - Data.QuadVertexBufferBase) * sizeof(QuadVertex);
            Data.QuadVertexBuffer->SetData(Data.QuadVertexBufferBase, dataSize);
            for (uint32_t i = 0; i < Data.TextureSlotIndex; i++) {
                Data.TextureSlots[i]->Bind(i);
            }
            Data.QuadShader->Bind();
            RendererCommand::DrawIndexed(Data.QuadVertexArray, Data.QuadIndexCount);
        }

    }

    void Renderer::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color, const Ref<Texture2D>& texture2D) {

        if (Data.QuadIndexCount >= RendererData::MaxIndices) {
            // FlushAndReset
        }

        float textureIndex = .0f;
        for (uint32_t i = 1; i < Data.TextureSlotIndex; i++) {
            if (Data.TextureSlots[i].get() == texture2D.get()) {
                textureIndex = (float)i;
                break;
            }
        }
        if (textureIndex == .0f && texture2D != nullptr) {
            textureIndex = (float)Data.TextureSlotIndex;
            Data.TextureSlots[Data.TextureSlotIndex++] = texture2D;
        }

        glm::mat4 model = glm::translate(position)
                          * glm::scale(glm::vec3(size.x, size.y, 1.0))
                          * glm::eulerAngleXYZ(glm::radians(.0f), glm::radians(.0f), glm::radians(.0f));

        for (int i = 0; i < 4; i++) {
            Data.QuadVertexBufferPtr->Position = model * Data.QuadVertexPositions[i];
            Data.QuadVertexBufferPtr->Color = color;
            Data.QuadVertexBufferPtr->TexIndex = textureIndex;
            Data.QuadVertexBufferPtr->TexCoord = Data.QuadVertexTexCoord[i];
            Data.QuadVertexBufferPtr++;
        }
        Data.QuadIndexCount += 6;

    }
} // lazy